package com.study.chunguard.common.utils;

import org.apache.commons.codec.binary.Hex;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

/**
 * AES工具类
 *
 * @author lifan
 * @date 2024/4/8 13:30
 */
public class AesUtil {
    private static final String ALGORITHM = "AES";
    private static final String AES_CBC_PADDING = "AES/CBC/PKCS5Padding";//AES/CBC/PKCS7Padding
    private static final String AES_ECB_PADDING = "AES/ECB/PKCS5Padding";//AES/ECB/PKCS7Padding

    public static final String AES_KEY = "ee1b7d9ca0b7d10d2cd27f35d34bb412";

    /**
     * 也可以通过这种方式获取密钥
     *
     * @param key
     * @return
     */
    private static SecretKey getSecretKey(byte[] key) {
        try {
            //获取指定的密钥生成器
            KeyGenerator keyGen = KeyGenerator.getInstance(ALGORITHM);
            //加密强随机数
            SecureRandom secureRandom = new SecureRandom();
            secureRandom.setSeed(key);
            //这里可以是128、192、256、越大越安全
            keyGen.init(256, secureRandom);
            return keyGen.generateKey();
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("AES获取密钥出现错误,算法异常");
        }
    }

    /**
     * Aes加密(CBC工作模式)
     *
     * @param key   密钥,key长度必须大于等于 3*8 = 24,并且是8的倍数
     * @param keyIv 初始化向量,keyIv长度必须等于16
     * @param data  明文
     * @return 密文
     * @throws Exception
     */
    public static byte[] encodeByCBC(byte[] key, byte[] keyIv, byte[] data)
            throws Exception {
        //获取SecretKey对象,也可以使用getSecretKey()方法
        Key secretKey = new SecretKeySpec(key, ALGORITHM);
        //获取指定转换的密码对象Cipher（参数：算法/工作模式/填充模式）
        Cipher cipher = Cipher.getInstance(AES_CBC_PADDING);
        //创建向量参数规范也就是初始化向量
        IvParameterSpec ips = new IvParameterSpec(keyIv);
        //用密钥和一组算法参数规范初始化此Cipher对象（加密模式）
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, ips);
        //执行加密操作
        return cipher.doFinal(data);
    }

    /**
     * Aes加密(ECB工作模式)
     *
     * @param key   密钥,key长度必须大于等于 3*8 = 24,并且是8的倍数
     * @param keyIv 初始化向量,keyIv长度必须等于16
     * @param data  密文
     * @return 明文
     * @throws Exception
     */
    public static byte[] decodeByCBC(byte[] key, byte[] keyIv, byte[] data)
            throws Exception {
        //获取SecretKey对象,也可以使用getSecretKey()方法
        Key secretKey = new SecretKeySpec(key, ALGORITHM);
        //获取指定转换的密码对象Cipher（参数：算法/工作模式/填充模式）
        Cipher cipher = Cipher.getInstance(AES_CBC_PADDING);
        //创建向量参数规范也就是初始化向量
        IvParameterSpec ips = new IvParameterSpec(keyIv);
        //用密钥和一组算法参数规范初始化此Cipher对象（加密模式）
        cipher.init(Cipher.DECRYPT_MODE, secretKey, ips);
        //执行加密操作
        return cipher.doFinal(data);
    }

    /**
     * Aes加密(ECB工作模式),不要IV
     *
     * @param key  密钥,key长度必须大于等于 3*8 = 24,并且是8的倍数
     * @param data 明文
     * @return 密文
     * @throws Exception
     */
    public static byte[] encodeByECB(byte[] key, byte[] data) throws Exception {
        //获取SecretKey对象,也可以使用getSecretKey()方法
        SecretKey secretKey = new SecretKeySpec(key, ALGORITHM);
        //获取指定转换的密码对象Cipher（参数：算法/工作模式/填充模式）
        Cipher cipher = Cipher.getInstance(AES_ECB_PADDING);
        //用密钥和一组算法参数规范初始化此Cipher对象（加密模式）
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);
        //执行加密操作
        return cipher.doFinal(data);
    }

    /**
     * Aes解密(ECB工作模式),不要IV
     *
     * @param key  密钥,key长度必须大于等于 3*8 = 24,并且是8的倍数
     * @param data 密文
     * @return 明文
     * @throws Exception
     */
    public static byte[] decodeByECB(byte[] key, byte[] data) throws Exception {
        //获取SecretKey对象,也可以使用getSecretKey()方法
        SecretKey secretKey = new SecretKeySpec(key, ALGORITHM);
        //获取指定转换的密码对象Cipher（参数：算法/工作模式/填充模式）
        Cipher cipher = Cipher.getInstance(AES_ECB_PADDING);
        //用密钥和一组算法参数规范初始化此Cipher对象（加密模式）
        cipher.init(Cipher.DECRYPT_MODE, secretKey);
        //执行加密操作
        return cipher.doFinal(data);
    }


    /**
     * mysql默认方式进行加密
     *
     * @param content 待加密字符串
     * @param strKey  aes密钥
     * @return 加密结果
     */
    public static String mysqlEncrypt(String content, String strKey) {
        try {
            SecretKey key = generateMySQLAESKey(strKey, "ASCII");
            Cipher cipher = Cipher.getInstance("AES");
            cipher.init(Cipher.ENCRYPT_MODE, key);
            byte[] cleartext = content.getBytes(StandardCharsets.UTF_8);
            byte[] ciphertextBytes = cipher.doFinal(cleartext);
            return new String(Hex.encodeHex(ciphertextBytes));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * mysql默认方式解密
     *
     * @param content 加密内容
     * @param aesKey  aes密钥
     * @return 解密后的字符串
     */
    public static String mysqlDecrypt(String content, String aesKey) {
        try {
            SecretKey key = generateMySQLAESKey(aesKey, "ASCII");
            Cipher cipher = Cipher.getInstance("AES");
            cipher.init(Cipher.DECRYPT_MODE, key);
            byte[] cleartext = Hex.decodeHex(content.toCharArray());
            byte[] ciphertextBytes = cipher.doFinal(cleartext);
            return new String(ciphertextBytes, StandardCharsets.UTF_8);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 生成aesKey
     *
     * @param key      aes密钥
     * @param encoding 字符集
     * @return 生成aesKey
     */
    public static SecretKeySpec generateMySQLAESKey(final String key, final String encoding) {
        try {
            final byte[] finalKey = new byte[16];
            int i = 0;
            for (byte b : key.getBytes(encoding))
                finalKey[i++ % 16] ^= b;
            return new SecretKeySpec(finalKey, "AES");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

}
